﻿using System;
using System.Linq;
using Csla;
using System.ComponentModel.DataAnnotations;
using BizControl.Resources;
using BizControl.DalLinq;
using BizControl.Library.BusinessObjects.Activities;
using System.Data.Linq;
using Csla.Data;
using BizControl.Library.BusinessObjects.Models;
using BizControl.Library.BusinessObjects.People;
using BizControl.Library.Security;
using BizControl.Library.BusinessObjects.Participants;
using Csla.Rules;

namespace BizControl.Library.BusinessObjects.Marks
{
  [Serializable()]
  public class ActivityMarkBO : BusinessBase<ActivityMarkBO>
  {
    #region Business Methods

    public const string ActivityMarkTagName = @"ActivityMark";

    private static readonly PropertyInfo<Guid> MarkIdProperty = RegisterProperty<Guid>(c => c.MarkId);
    [Required(AllowEmptyStrings = false, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"MarkIdRequiredPropertyError")]
    public Guid MarkId
    {
      get { return GetProperty(MarkIdProperty); }
      private set { SetProperty(MarkIdProperty, value); }
    }

    private static readonly PropertyInfo<string> NameProperty = RegisterProperty<string>(c => c.Name);
    [Required(AllowEmptyStrings = false, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"MarkNameRequiredPropertyError")]
    [Display(ResourceType = typeof(PropertiesNames), Name = @"MarkNameProperty")]
    public string Name
    {
      get { return GetProperty(NameProperty); }
      set { SetProperty(NameProperty, value); }
    }

    private static readonly PropertyInfo<string> TagProperty = RegisterProperty<string>(c => c.Tag);
    [Display(ResourceType = typeof(PropertiesNames), Name = @"MarkTagProperty")]
    public string Tag
    {
      get { return ActivityMarkTagName; }
    }

    private static readonly PropertyInfo<DateTime?> DateSetProperty = RegisterProperty<DateTime?>(c => c.DateSet);
    [Display(ResourceType = typeof(PropertiesNames), Name = @"MarkDateSetProperty")]
    public DateTime? DateSet
    {
      get { return GetProperty(DateSetProperty); }
      private set { SetProperty(DateSetProperty, value); }
    }

    private static readonly PropertyInfo<bool> IsActiveProperty = RegisterProperty<bool>(c => c.IsActive, null, false);
    [Required(AllowEmptyStrings = false, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"MarkIsActiveRequiredPropertyError")]
    [Display(ResourceType = typeof(PropertiesNames), Name = @"MarkIsActiveProperty")]
    public bool IsActive
    {
      get { return GetProperty(IsActiveProperty); }
      private set
      {
        SetProperty(IsActiveProperty, value);
        if (ReadProperty(IsActiveProperty))
          DateSet = DateTime.Now;
        OnPropertyChanged(StateProperty);
      }
    }

    private static readonly PropertyInfo<Guid?> ModelProperty = RegisterProperty<Guid?>(c => c.ModelId);
    [Required(AllowEmptyStrings = false, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"ModelRequiredError")]
    [Display(ResourceType = typeof(PropertiesNames), Name = @"ModelIdProperty")]
    internal Guid? ModelId
    {
      get { return GetProperty(ModelProperty); }
      set { SetProperty(ModelProperty, value); }
    }

    private static readonly PropertyInfo<Guid?> ActivityProperty = RegisterProperty<Guid?>(c => c.ActivityId);
    [Required(AllowEmptyStrings = false, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"ActivityRequiredPropertyError")]
    [Display(ResourceType = typeof(PropertiesNames), Name = @"ActivityIdProperty")]
    Guid? ActivityId
    {
      get { return GetProperty(ActivityProperty); }
      set { SetProperty(ActivityProperty, value); }
    }

    private static readonly PropertyInfo<DateTime?> DueDateProperty = RegisterProperty<DateTime?>(c => c.DueDate);
    [Display(ResourceType = typeof(PropertiesNames), Name = @"ActivityMarkDueDateProperty")]
    public DateTime? DueDate
    {
      get { return GetProperty(DueDateProperty); }
      set { SetProperty(DueDateProperty, value); OnPropertyChanged(StateProperty); }
    }

    private static readonly PropertyInfo<int> WeightProperty = RegisterProperty<int>(c => c.Weight, null, default(int));
    [Display(ResourceType = typeof(PropertiesNames), Name = @"ActivityMarkWeightProperty")]
    public int Weight
    {
      get { return GetProperty(WeightProperty); }
      set
      {
        SetProperty(WeightProperty, value);
        if (Parent != null)
          ((this.Parent as ActivityMarks).Parent as ActivityBO).UpdateProgress();
      }
    }

    private static readonly PropertyInfo<ParticipantInfo> ResponsibleParticipantProperty = RegisterProperty<ParticipantInfo>(c => c.ResponsibleParticipant);
    [Display(ResourceType = typeof(PropertiesNames), Name = @"ResponsibleParticipantProperty")]
    public ParticipantInfo ResponsibleParticipant
    {
      get { return GetProperty(ResponsibleParticipantProperty); }
      set { SetProperty(ResponsibleParticipantProperty, value); }
    }

    private static readonly PropertyInfo<ParticipantInfo> NextParticipantProperty = RegisterProperty<ParticipantInfo>(c => c.NextParticipant);
    [Display(ResourceType = typeof(PropertiesNames), Name = @"NextParticipantProperty")]
    public ParticipantInfo NextParticipant
    {
      get { return GetProperty(NextParticipantProperty); }
      set { SetProperty(NextParticipantProperty, value); }
    }

    private static readonly PropertyInfo<bool> HasDetailsProperty = RegisterProperty<bool>(c => c.HasDetails);
    public bool HasDetails
    {
      get { return Details.Count > 0; }
    }

    private static readonly PropertyInfo<MarkState> StateProperty = RegisterProperty<MarkState>(c => c.State);
    public MarkState State
    {
      get
      {
        MarkState state;
        if (IsActive) state = MarkState.Completed;
        else state = MarkState.NotCompleted;
        if ((!IsActive && DueDate < DateTime.Now)
          || (IsActive && DueDate < DateSet)) state = state | MarkState.Overdue;
        if (state.HasFlag(MarkState.NotCompleted))
        {
          var parent = this.Parent as ActivityMarks;
          if (parent == null) return state;
          var activity = parent.Parent as ActivityBO;
          if (activity == null) return state;
          foreach (var condition in Conditions)
          {
            if (condition.Type == ConditionType.MarkRequired && condition.RequiredMark != null)
              if (activity.Marks[condition.RequiredMark.Id] != null && !activity.Marks[condition.RequiredMark.Id].IsActive)
              {
                state = state | MarkState.NotStarted;
                break;
              }
          }
        }
        return state;
      }
    }

    private static readonly PropertyInfo<ActivityMarkList> AvailableAsConditionActivityMarksProperty = RegisterProperty<ActivityMarkList>(c => c.AvailableAsConditionActivityMarks);
    public ActivityMarkList AvailableAsConditionActivityMarks
    {
      get
      {
        ActivityMarks parentActivityMarks = this.Parent as ActivityMarks;
        ActivityMarkList resultList = ActivityMarkList.NewActivityMarkList();
        if (parentActivityMarks != null)
        {
          ActivityBO parent = parentActivityMarks.Parent as ActivityBO;
          if (parent != null)
          {
            resultList.AllowChange();
            foreach (var mark in parent.Marks)
            {
              bool add = mark.MarkId != MarkId;
              if (add)
              {
                // check for cycles
              }
              if (add)
                resultList.Add(new MarkInfo(mark));
            }
            resultList.ForbidChange();
          }
        }
        return resultList;
      }
    }

    private static readonly PropertyInfo<ActivityList> AvailableAsConditionActivitiesProperty = RegisterProperty<ActivityList>(c => c.AvailableAsConditionActivities);
    public ActivityList AvailableAsConditionActivities
    {
      get
      {
        return ActivityList.NewActivityList();
      }
    }

    private static readonly PropertyInfo<PersonMarkList> AvailableAsConditionPersonMarksProperty = RegisterProperty<PersonMarkList>(c => c.AvailableAsConditionPersonMarks);
    public PersonMarkList AvailableAsConditionPersonMarks
    {
      get
      {
        return PersonMarkList.GetPersonMarkList();
      }
    }

    private static readonly PropertyInfo<bool> CanSetCompleteProperty = RegisterProperty<bool>(c => c.CanSetComplete);
    public bool CanSetComplete
    {
      get
      {
        if (IsActive) return false;
        if (this.Parent != null)
        {
          foreach (var condition in Conditions)
          {
            if (condition.Type == ConditionType.MarkRequired && condition.RequiredMark != null)
            {
              var parent = (this.Parent as ActivityMarks);
              var activity = parent.Parent as ActivityBO;
              if (activity == null) return false;
              var mark = activity.Marks[condition.RequiredMark.Id];
              if (mark != null && !mark.IsActive)
                return false;
            }
          }
          if (ResponsibleParticipant == null || ResponsibleParticipant.IsEmpty() || ResponsibleParticipant.Person.Id == SecurityChecks.GetCurrentUser().PersonId)
            return true;
        }
        return false;
      }
      set
      {
        OnPropertyChanged(CanSetCompleteProperty);
      }
    }

    #region Common properties

    private static readonly PropertyInfo<bool> IsDeletedFieldProperty = RegisterProperty<bool>(c => c.IsDeletedField, null, false);
    [Required(AllowEmptyStrings = false, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"IsDeletedRequiredError")]
    [Display(ResourceType = typeof(PropertiesNames), Name = @"IsDeletedFieldProperty")]
    public bool IsDeletedField
    {
      get { return GetProperty(IsDeletedFieldProperty); }
      private set { SetProperty(IsDeletedFieldProperty, value); }
    }

    private static readonly PropertyInfo<PersonInfo> CreatedByProperty = RegisterProperty<PersonInfo>(c => c.CreatedBy);
    [Required(AllowEmptyStrings = false, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"CreatedByRequiredPropertyError")]
    [Display(ResourceType = typeof(PropertiesNames), Name = @"CreatedByProperty")]
    public PersonInfo CreatedBy
    {
      get { return GetProperty(CreatedByProperty); }
      private set { SetProperty(CreatedByProperty, value); }
    }

    private static readonly PropertyInfo<DateTime> CreatedOnProperty = RegisterProperty<DateTime>(c => c.CreatedOn);
    [Required(AllowEmptyStrings = false, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"CreatedOnRequiredPropertyError")]
    [Display(ResourceType = typeof(PropertiesNames), Name = @"CreatedOnProperty")]
    public DateTime CreatedOn
    {
      get { return GetProperty(CreatedOnProperty); }
      private set { SetProperty(CreatedOnProperty, value); }
    }

    private static readonly PropertyInfo<DateTime> LastChangedProperty = RegisterProperty<DateTime>(c => c.LastChanged);
    [Required(AllowEmptyStrings = false, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"LastChangedRequiredPropertyError")]
    [Display(ResourceType = typeof(PropertiesNames), Name = @"LastChangedProperty")]
    public DateTime LastChanged
    {
      get { return GetProperty(LastChangedProperty); }
      private set { SetProperty(LastChangedProperty, value); }
    }

    private static readonly PropertyInfo<PersonInfo> LastChangedByProperty = RegisterProperty<PersonInfo>(c => c.LastChangedBy);
    [Required(AllowEmptyStrings = false, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"LastChangedByRequiredPropertyError")]
    [Display(ResourceType = typeof(PropertiesNames), Name = @"LastChangedByProperty")]
    public PersonInfo LastChangedBy
    {
      get { return GetProperty(LastChangedByProperty); }
      private set { SetProperty(LastChangedByProperty, value); }
    }

    private static readonly PropertyInfo<byte[]> TimeStampProperty = RegisterProperty<byte[]>(c => c.TimeStamp);
    private byte[] TimeStamp
    {
      get { return GetProperty(TimeStampProperty); }
      set { SetProperty(TimeStampProperty, value); }
    }

    #endregion

    #region Children

    private static readonly PropertyInfo<MarkDetails> DetailsProperty =
      RegisterProperty<MarkDetails>(p => p.Details, RelationshipTypes.Child | RelationshipTypes.LazyLoad);
    public MarkDetails Details
    {
      get
      {
        if (!(FieldManager.FieldExists(DetailsProperty)))
          Details = MarkDetails.NewMarkDetails(new MarkInfo(this));
        return GetProperty(DetailsProperty);
      }
      internal set
      {
        LoadProperty(DetailsProperty, value);
        OnPropertyChanged(DetailsProperty);
      }
    }

    private static readonly PropertyInfo<MarkConditions> ConditionsProperty =
      RegisterProperty<MarkConditions>(p => p.Conditions, RelationshipTypes.Child | RelationshipTypes.LazyLoad);
    public MarkConditions Conditions
    {
      get
      {
        if (!(FieldManager.FieldExists(ConditionsProperty)))
          Conditions = MarkConditions.NewMarkConditions(new MarkInfo(this));
        return GetProperty(ConditionsProperty);
      }
      internal set
      {
        LoadProperty(ConditionsProperty, value);
        OnPropertyChanged(ConditionsProperty);
      }
    }

    #endregion

    #region Methods

    private static readonly MethodInfo CheckDueDateMethod = RegisterMethod(typeof(ActivityMarkBO), "CheckDueDate");
    public void CheckDueDate()
    {
      //CanExecuteMethod(CheckDueDateMethod);
      BusinessRules.CheckRules(DueDateProperty);
      OnPropertyChanged(DueDateProperty);
    }

    private static readonly MethodInfo SetCompleteMethod = RegisterMethod(typeof(ActivityMarkBO), "SetComplete");
    public void SetComplete()
    {
      //CanExecuteMethod(CheckDueDateMethod);
      if (!CanSetComplete) return;
      if (!IsActive)
        IsActive = true;
      else
        IsActive = false;
      if (Parent != null)
      {
        ((this.Parent as ActivityMarks).Parent as ActivityBO).ResponsiblePerson = IsActive == true ? 
          new PersonInfo(NextParticipant.Person) : new PersonInfo(ResponsibleParticipant.Person);
        ((this.Parent as ActivityMarks).Parent as ActivityBO).UpdateProgress();
      }
    }

    #endregion

    internal void LoadData(Mark markDB)
    {
      if (markDB == null) return;
      MarkId = markDB.MarkId;
      Name = markDB.Name;
      DateSet = markDB.DateSet;
      IsActive = markDB.IsActive;
      DueDate = markDB.DueDate;
      Weight = markDB.Weight;
      ResponsibleParticipant = new ParticipantInfo(markDB.ResponsibleParticipantRef);
      NextParticipant = new ParticipantInfo(markDB.NextParticipantRef);

      // Common properties
      CreatedBy = new PersonInfo(markDB.CreatedByRef);
      LastChangedBy = new PersonInfo(markDB.LastChangedByRef);

      CreatedOn = markDB.CreatedOn;
      LastChanged = markDB.LastChanged;

      IsDeletedField = markDB.IsDeleted;
      TimeStamp = markDB.TimeStamp.ToArray();
    }

    public override string ToString()
    {
      return Name;
    }

    private void LinkMarkWithActivity(ActivityBO activity)
    {
      ActivityId = activity.ActivityId;
      var currentModel = ((BizControlPrincipal)ApplicationContext.User).Model;
      if (currentModel == null) throw new Exception(ExceptionsMessages.ModelIsNotSet);
      if (currentModel.ModelId != activity.ModelId) throw new Exception(ExceptionsMessages.ModelsAreNotEqual);
      ModelId = currentModel.ModelId;
    }

    internal void UpdateCanComplete()
    {
      OnPropertyChanged(CanSetCompleteProperty);
    }

    internal void UpdateState()
    {
      OnPropertyChanged(StateProperty);
    }

    internal void UpdateActivityCanComplete()
    {
      var parent = this.Parent as ActivityMarks;
      if (parent != null)
      {
        var activity = this.Parent as ActivityBO;
        if (activity != null)
          activity.UpdateCanComplete();
      }
    }

    #endregion

    #region Business Rules

    protected override void AddBusinessRules()
    {
      base.AddBusinessRules();
      BusinessRules.AddRule(new MarkDueDateGTActivityDueDate { PrimaryProperty = DueDateProperty });
      BusinessRules.AddRule(new UpdateCanSetComplete { PrimaryProperty = ResponsibleParticipantProperty });
      BusinessRules.AddRule(new UpdateCanSetComplete { PrimaryProperty = ConditionsProperty });
      BusinessRules.AddRule(new UpdateCanSetCompleteForAllMarks { PrimaryProperty = IsActiveProperty });
      BusinessRules.AddRule(new UpdateStatesForAllMarks { PrimaryProperty = IsActiveProperty });
      BusinessRules.AddRule(new UpdateStatesForAllMarks { PrimaryProperty = ConditionsProperty });
      BusinessRules.AddRule(new UpdateCanSetCompleteActivity { PrimaryProperty = IsActiveProperty });
    }

    private class UpdateCanSetCompleteActivity : BusinessRule
    {
      protected override void Execute(RuleContext context)
      {
        var target = (ActivityMarkBO)context.Target;
        var parent = target.Parent as ActivityMarks;
        if (parent != null)
        {
          var activity = parent.Parent as ActivityBO;
          if (activity != null)
            activity.UpdateCanComplete();
        }
      }
    }

    private class UpdateStatesForAllMarks : BusinessRule
    {
      protected override void Execute(RuleContext context)
      {
        var target = (ActivityMarkBO)context.Target;
        var parent = target.Parent as ActivityMarks;
        if (parent != null)
        {
          var activity = parent.Parent as ActivityBO;
          foreach (var mark in activity.Marks)
          {
            mark.UpdateState();
          }
        }
      }
    }

    private class UpdateCanSetCompleteForAllMarks : BusinessRule
    {
      protected override void Execute(RuleContext context)
      {
        var target = (ActivityMarkBO)context.Target;
        var parent = target.Parent as ActivityMarks;
        if (parent != null)
        {
          var activity = parent.Parent as ActivityBO;
          foreach (var mark in activity.Marks)
          {
            mark.UpdateCanComplete();
          }
        }
      }
    }

    private class UpdateCanSetComplete : BusinessRule
    {
      protected override void Execute(RuleContext context)
      {
        var target = (ActivityMarkBO)context.Target;
        target.UpdateCanComplete();
      }
    }

    private class MarkDueDateGTActivityDueDate : BusinessRule
    {
      protected override void Execute(RuleContext context)
      {
        var target = (ActivityMarkBO)context.Target;

        if (target.Parent != null)
        {
          if ((target.Parent as ActivityMarks).Parent != null)
          {
            var parent = ((target.Parent as ActivityMarks).Parent as ActivityBO);
            var markDueDate = target.ReadProperty(DueDateProperty);
            var activityDueDate = parent.DueDate;
            if (markDueDate.HasValue && markDueDate > activityDueDate)
              context.AddErrorResult(ValidationErrors.MarkDueDateGTActivityDueDateError);
          }
        }
      }
    }

    #endregion

    #region  Data Access

    private void Child_Create(ActivityBO activity)
    {
      if (activity == null) throw new ArgumentNullException(ExceptionsMessages.ActivityNullReference);
      var currentUser = SecurityChecks.GetCurrentUser();
      using (BypassPropertyChecks)
      {
        MarkId = Guid.NewGuid();
        LinkMarkWithActivity(activity);
        CreatedBy = new PersonInfo(currentUser);
        LastChangedBy = new PersonInfo(currentUser);
        CreatedOn = DateTime.Now;
        ResponsibleParticipant = new ParticipantInfo(ParticipantBO.GetEmptyParticipant());
        NextParticipant = new ParticipantInfo(ParticipantBO.GetEmptyParticipant());
      }
      base.Child_Create();
    }

    private void Child_Fetch(Mark data, ActivityBO activity)
    {
      if (activity == null) throw new ArgumentNullException(ExceptionsMessages.ActivityNullReference);
      using (BypassPropertyChecks)
      {
        LinkMarkWithActivity(activity);
        LoadData(data);
        if (data != null)
        {
          Details = MarkDetails.GetMarkDetails(data.MarkDetails.ToArray(), new MarkInfo(this));
          Conditions = MarkConditions.GetMarkConditions(data.MarkConditions
            .OrderBy(c => c.RequiredMarkRef == null ? c.RequiredActivityRef.DueDate : c.RequiredMarkRef.DueDate).ToArray(), new MarkInfo(this));
        }
      }
    }

    private void Child_Insert()
    {
      using (var ctx = ContextManager<BusinessControl2DataContext>.GetManager(Database.BizControlDB))
      {
        Binary lastTimeStamp = null;
        LastChanged = DateTime.Now;
        using (BypassPropertyChecks)
        {
          ctx.DataContext.AddMark(
            MarkId,
            ResponsibleParticipant == null || ResponsibleParticipant.IsEmpty() ? (Guid?)null : ResponsibleParticipant.Id,
            NextParticipant == null || NextParticipant.IsEmpty() ? (Guid?)null : NextParticipant.Id,
            ActivityId,
            ModelId,
            Name,
            Tag,
            DueDate,
            Weight,
            IsActive,
            DateSet,
            CreatedBy == null || CreatedBy.IsEmpty() ? (Guid?)null : CreatedBy.Id,
            IsDeletedField,
            CreatedOn,
            LastChanged,
            LastChangedBy == null || LastChangedBy.IsEmpty() ? (Guid?)null : LastChangedBy.Id,
            ref lastTimeStamp);
          TimeStamp = lastTimeStamp.ToArray();
        }
        // update child objects
        //FieldManager.UpdateChildren(this);
        DataPortal.UpdateChild(Details, this);
      }
    }

    private void Child_Update()
    {
      using (var ctx = ContextManager<BusinessControl2DataContext>.GetManager(Database.BizControlDB))
      {
        if (IsSelfDirty)
        {
          Binary lastTimeStamp = null;
          LastChangedBy = new PersonInfo(SecurityChecks.GetCurrentUser());
          LastChanged = DateTime.Now;
          using (BypassPropertyChecks)
          {
            ctx.DataContext.UpdateMark(
              MarkId,
              ResponsibleParticipant == null || ResponsibleParticipant.IsEmpty() ? (Guid?)null : ResponsibleParticipant.Id,
              NextParticipant == null || NextParticipant.IsEmpty() ? (Guid?)null : NextParticipant.Id,
              ActivityId,
              ModelId,
              Name,
              Tag,
              DueDate,
              Weight,
              IsActive,
              DateSet,
              IsDeletedField,
              LastChanged,
              LastChangedBy == null || LastChangedBy.IsEmpty() ? (Guid?)null : LastChangedBy.Id,
              TimeStamp,
              ref lastTimeStamp);
            TimeStamp = lastTimeStamp.ToArray();
          }
        }
      }
      // update child objects
      FieldManager.UpdateChildren(this);
    }

    private void Child_DeleteSelf()
    {
      using (BypassPropertyChecks)
      {
        IsDeletedField = true;
        Child_Update();
      }
    }

    #endregion

    #region  Factory Methods

    internal static ActivityMarkBO NewActivityMark(ActivityBO activity)
    {
      return Csla.DataPortal.CreateChild<ActivityMarkBO>(activity);
    }

    internal static ActivityMarkBO GetActivityMark(Mark data, ActivityBO activity)
    {
      return Csla.DataPortal.FetchChild<ActivityMarkBO>(data, activity);
    }

    private ActivityMarkBO()
    { /* require use of factory methods */ }

    #endregion

  }
}
